#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/debugfs.h>
#include <linux/mutex.h>

MODULE_LICENSE("Dual BSD/GPL");

#define MISCNAME	"eudyptula"
#define ASSIGNED_ID	"09b0d224b5bb"
#define ID_LEN		20

static struct dentry *eudyptula_debug_dir;
static uint8_t foo_buf[PAGE_SIZE];
static DEFINE_MUTEX(foo_buf_mutex);

static ssize_t hello_id_write(struct file *file, const char __user *buf,
				size_t count, loff_t *pos)
{
	uint8_t id_buf[ID_LEN] = {0};
	ssize_t res;

	res = simple_write_to_buffer(id_buf, ID_LEN, pos, buf, count);
	if (res < 0)
		return res;

	if (!strncmp(ASSIGNED_ID, id_buf, ID_LEN))
		return res;
	else
		return -EINVAL;
}

static ssize_t hello_id_read(struct file *file, char __user *buf,
				size_t count, loff_t *pos)
{
	return simple_read_from_buffer(buf, count, pos,
			ASSIGNED_ID, sizeof(ASSIGNED_ID));
}

static ssize_t hello_foo_write(struct file *file, const char __user *buf,
				size_t count, loff_t *pos)
{
	ssize_t res;

	if (count > PAGE_SIZE)
		return -ENOSPC;

	mutex_lock(&foo_buf_mutex);
	res = simple_write_to_buffer(foo_buf, PAGE_SIZE, pos, buf, count);
	if (res < 0)
		pr_err("Failed to write to buffer\n");

	mutex_unlock(&foo_buf_mutex);

	return res;
}

static ssize_t hello_foo_read(struct file *file, char __user *buf,
				size_t count, loff_t *pos)
{
	ssize_t res;

	mutex_lock(&foo_buf_mutex);
	res = simple_read_from_buffer(buf, count, pos,
				foo_buf, sizeof(PAGE_SIZE));
	mutex_unlock(&foo_buf_mutex);

	return res;
}

static ssize_t hello_jiffies_read(struct file *file, char __user *buf,
				size_t count, loff_t *pos)
{
	char str[20];

	snprintf(str, 20, "%llu", (unsigned long long)jiffies);
	return simple_read_from_buffer(buf, count, pos,
					str, strlen(str));
}

static const struct file_operations hello_id_ops = {
	.owner =  THIS_MODULE,
	.write = hello_id_write,
	.read = hello_id_read,
};

static const struct file_operations hello_jiffies_ops = {
	.owner =  THIS_MODULE,
	.read = hello_jiffies_read,
};

static const struct file_operations hello_foo_ops = {
	.owner =  THIS_MODULE,
	.write = hello_foo_write,
	.read = hello_foo_read,
};

static int hello_init(void)
{
	struct dentry *fentry;

	pr_debug("Hello, World!\n");
	eudyptula_debug_dir = debugfs_create_dir("eudyptula", NULL);
	if (!eudyptula_debug_dir) {
		pr_err("Error creating eudyptula directory.\n");
		return -ENOMEM;
	}

	fentry = debugfs_create_file("id", S_IFREG | S_IRUGO | S_IWUGO,
					eudyptula_debug_dir, NULL,
					&hello_id_ops);
	if (!fentry) {
		pr_err("Error creating id file.\n");
		return -ENOMEM;
	}

	fentry = debugfs_create_file("jiffies", S_IFREG | S_IRUGO,
					eudyptula_debug_dir, NULL,
					&hello_jiffies_ops);
	if (!fentry) {
		pr_err("Error creating jiffies file.\n");
		return -ENOMEM;
	}

	fentry = debugfs_create_file("foo", S_IFREG | S_IWUSR | S_IRUGO,
					eudyptula_debug_dir, NULL,
					&hello_foo_ops);
	if (!fentry) {
		pr_err("Error creating foo file.\n");
		return -ENOMEM;
	}

	return 0;
}

static void hello_exit(void)
{
	pr_debug("See you, World!\n");
	debugfs_remove_recursive(eudyptula_debug_dir);
}

module_init(hello_init);
module_exit(hello_exit);
